<?php
/**
  * Шаблонный движок.
  *
  * прекомпиляция шаблонов в PHP
  * php actions, conditions, includes

  TemplateEngine( array $config )

  ---------

  // Работа с доменом переменных

  * Get( $key )            -- получить значение (намеренно без ссылки)
  * Set( $key, $value=1 )  -- установить значение ключу
  * SetRef( $key, &$ref )  -- установить значение ссылкой
  * Append( $key, $value ) -- дописать в конец
  * Is( $key )             -- true, если этот ключ хоть что-то значил (isset is a keyword)
  * Free( $key="" )        -- очистка домена или unset переменной
      - $key   -- имя переменной (поля), если пустое, то очищается весь домен
  * Load( $domain )        -- загрузка массива в ключи

  // Парсинг шаблонов

  * Parse( $tpl_name, $store_to="", $append=0 ) -- парсинг шаблона и возвращение результата.;
                                                              Возвращает строку-результат
      - $tpl_name    -- имя шаблонки, например "front.html:Menu" или "front.html"
      - $store_to    -- если установлено, то результат также сохраняется в переменную домена с таким именем
      - $append      -- если непустое $store_to, то результат не стирает значение переменной, а дописывается в конец

  * ParseInstant( $template_content ) -- "отпарсить" контент, данный как параметр (а не брать из файла)

  * _SpawnCompiler() -- порождает компиляторскую часть

  * findTemplate( $tpl_filename ) -- возвращает уровень и полный путь к файлу,
                                                                   в котором лежит кэш этого (под)шаблона

  // Шкуры

  * Skin( $skin_name="" )   -- переключить систему в другую шкуру

  // Языковые конструкты шаблонного движка

  - Action( $action_name, &$params ) -- вызов действия
      - $action_name -- lowered case названия действия

  // Частоиспользуемые actions, которые имеет смысл вшить прямо сюда:
  * action_ActionName -- "системные" акшны, вшитые прямо в движок для скорости


  // Доп. параметры

  * $this->msg -- может быть объект класса MessageSet


  ---------

  NB: все шаблоны должны быть .html -- это ускоряет парсинг


=============================================================== v.2 (kuso@npj, zharik@npj)
*/
define ('TPL_APPEND', 1);
define ('TPL_COMPILE_NEVER',  0);
define ('TPL_COMPILE_SMART',  1);
define ('TPL_COMPILE_ALWAYS', 2);

define ('TEC_PARAM_NAME',   '_n');
define ('TEC_PARAM_CALLER', '_c');
define ('TEC_PARAM_PLAIN',  '_p');
define ('TEC_PARAM_INSTANT',  'instant');

define ('TPL_DOMAIN_TYPE',   '_t');


class Tpl
{
	/**
	 * Может быть объект класса RL_MessageSet
	 *
	 * @var RL_MessageSet
	 */
  public $msg = false;

  /**
   * Отладка
   *
   * @var RL_Debug
   */
  public $debug;

	static $default_config = array(
		'tpl_cache_dir' => 'cache/',
		'tpl_data_dir' => 'data/',
		'tpl_templates_dir' => 'templates/',
		'tpl_compile' => TPL_COMPILE_SMART,
		'tpl_dirs' => array(),
		'tpl_skin' => '',                                  // (*) for no-skin-mode which is default
		// стандарты шаблонного движка
		'tpl_action_prefix' => 'tpl_',
		'tpl_template_prefix' => 'tpl_',
		'tpl_template_sepfix' => '_',
		'tpl_action_file_prefix' => '@@',
		'tpl_template_file_prefix' => '@',
		'tpl_cache_prefix' => '@',                       // с этого значения начинаются в кэше все переменные TE
		'tpl_prefix' => '{{',
		'tpl_postfix' => '}}',
		'tpl_instant' => '~',
		'tpl_construct_action' => '!',                   // {{!text Test}}
		'tpl_construct_action2' => '!!',                 // {{!!text}}Test{{!!/text}}
		'tpl_construct_if' => '?',                       // {{?var}} or {{?!var}}
		'tpl_construct_ifelse' => '?:',                  // {{?:}}
		'tpl_construct_object' => '#.',                  // {{#obj.property}}
		'tpl_construct_tplt' => 'TPL:',                  // {{TPL:Name}}...{{/TPL:Name}}
		'tpl_construct_tplt2' => ':',                    // {{:Name}}...{{/:Name}}   -- ru@jetstyle бесят буквы TPL в капсе
		'tpl_construct_comment' => '#',                  // <!-- # persistent comment -->
		// lucky:
		'tpl_construct_standard_camelCase' => True,      // True, значит считаем, что кодим в стандарте CamelCase
		// т.е. методы объектов имеют вид
		// $o->SomeValue(),
		// иначе будем считать, что обкурились ruby
		// $o->some_value()
		// (на случай, если ru станет заведовать разработкой)
		'tpl_construct_standard_getter_prefix' => 'get', // lucky: префиксы для getter'ов

		'tpl_arg_prefix' => '',
		'tpl_arg_postfix' => '',

		'tpl_instant_plugins' => array( 'dummy' ), // plugins that are ALWAYS instant

		'tpl_shortcuts' => array(
			'=>' => array('=', ' typografica=1'),
			'=<' => array('=', ' strip_tags=1'),
			'+>' => array('+', ' typografica=1'),
			'+<' => array('+', ' strip_tags=1'),
			'*' => '$*.',
			'@' => '!include @',
			//'=' => '!_ tag=',
			//'+' => '!message ',
		),
		'tpl_root_href' => '/',          // -- как добраться по URL до них

// FIXME : 2008-02-21 : lucky
// придумать имя
// универсальный префикс для именования файлов кешей.
		'environment' => '',

		// Для плагина !connect
		'CONNECT' => array(),
		// Для плагина !inline
		'INLINE' => array('head'=>array(), 'onload'=>array()),
		'disable_gz' => NULL,
		'static_file_mode' => 0644,
		'script_file_mode' => 0640,
	);

  public function __construct($config=array())
  /**
   * Конструктор
   *
   * @param array $config
   */
  {
    $this->domain = array();

		// инициализировать из конфига
		$c = $config + self::$default_config;
		foreach($c as $k=>$v) { $this->$k = $v; }

		if (!$this->disable_gz && !function_exists('gzencode')) $this->disable_gz = true;

    // выбрать шкуру
    $this->setSkin ($this->tpl_skin);
  }

  // Работа с доменом переменных -------------------------------------------------------------

	public function get( $key )
	/**
	 * Получить значение (намеренно без ссылки)
	 *
	 * @param string $key
	 * @return mixed
	 */
	{
		return isset($this->domain[$key]) ? $this->domain[$key] : "" ;
	}
	public function set($key, $value, $type=NULL)
	/**
	 * Установить значение ключу
	 *
	 * @param string $key
	 * @param mixed $value
	 */
	{
		$this->domain[$key] = $value;
		$this->domain[TPL_DOMAIN_TYPE][$key] = $type;
	}
	public function setRef( $key, &$ref, $type=NULL)
	/**
	 * установить значение ссылкой
	 *
	 * @param string $key
	 * @param mixed $ref
	 */
	{
		$this->domain[$key] = &$ref;
		$this->domain[TPL_DOMAIN_TYPE][$key] = $type;
	}
	public function append($key, $value)
	/**
	 * дописать в конец
	 *
	 * @param string $key
	 * @param mixed $value
	 */
	{
		if (!isset($this->domain[$key])) {
			$this->domain[$key] = '';
			$this->domain[TPL_DOMAIN_TYPE][$key] = $this->content_type;
		}
		$this->domain[$key] .= $value;
	}
	public function is( $key )
	/**
	 * true, если этот ключ хоть что-то значил
	 *
	 * @param string $key
	 * @return bool
	 */
	{
		return isset( $this->domain[$key] );
	}
	public function free( $key = NULL )
	/**
	 * очистка домена или unset переменной
	 *
	 * @param string $key
	 */
	{
		if ($key === NULL) {
			$this->domain = array();
		} elseif( is_array($key) ) {
			foreach($key as $k) {
				unset ($this->domain[$k], $this->domain[TPL_DOMAIN_TYPE][$key]);
			}
		} else {
			unset ($this->domain[$key], $this->domain[TPL_DOMAIN_TYPE][$key]);
		}
	}
	public function load($domain)
	/**
	 * Загрузка массива в ключи
	 *
	 * @param array $domain
	 */
	{
		if (isset($domain[TPL_DOMAIN_TYPE])) {
			$ts = $domain[TPL_DOMAIN_TYPE];
			unset($domain[TPL_DOMAIN_TYPE]);
		} else {
			$ts = array();
		}
		foreach($domain as $k=>$v) {
			$this->set( $k, $v );
		}
		$this->domain[TPL_DOMAIN_TYPE] = $ts + $this->domain[TPL_DOMAIN_TYPE];
	}

  // Шкуры ------------------------------------------------------------------------------------

  public function setSkin( $skin_name = "" )
  /**
   * переключить систему в другую шкуру (увеличив глубину стека)
   *
   * @param string $skin_name
   * @return void
   */
  {
    $this->_skin = $skin_name;
		$this->_rebuildEnvironment();

    return $this;
  }

  // Параметры шаблонов -----------------------------------------------------------------------

  /**
   * Стек для набора шаблонных переменных.
   *
   * @var array
   */
	public $stack = array();
  /**
   * Набор шаблонных переменных
   *
   * @var array
   */
  public $domain = array();

	public function push ()
	{
		$this->stack[] = array($this->domain, $this->autoescape, $this->content_type);
	} // end of method push
	public function pop ()
	{
		list($this->domain, $this->autoescape, $this->content_type) = array_pop($this->stack);
	} // end of method pop
	public function call ($func, &$params=array(), $store_to=NULL, $append=false)
	{
		ob_start();

		$this->push();
		if (is_array($func)) { $o=$func[0];$m=$func[1];$res=$o->$m($params, $this); }
		else $res = $func($params, $this);
		$this->pop();

		if (!isset($res)) $res = trim(ob_get_contents());
		ob_end_clean();

    //7. $store_to & $append
    if (isset($store_to)) {
      if ($append) {
				$this->append($store_to, $res);
      } else {
        $this->set($store_to, $res, $this->content_type);
      }
    }
		return $res;
	} // end of method call

  // Парсинг шаблонов --------------------------------------------------------------------------

	public function parse ($tpl_name, $store_to=NULL, $append=false)
	{
		$params = array();
		return $this->call($this->parseTemplate($tpl_name), $params, $store_to, $append);
	} // end of method parse
	public function action ($action_name, &$params=array())
	{
		return $this->call($this->parseAction($action_name), $params);
	} // end of method action
	public function parseString ($tpl_string, $store_to=NULL, $append=false)
	{
		$params = array();
		return $this->call($this->parseStr($tpl_string), $params, $store_to, $append);
	} // end of method parse

	public function parseStr ($tpl_string)
	/**
	 * "отпарсить" контент, данный как параметр (а не брать из файла)
	 *
	 * @param string $template_content
	 * @return string
	 */
	{
		$tpl_name = md5($tpl_string);

		if (isset($this->ep_actions[$tpl_name]))
			return $this->ep_actions[$tpl_name];

		$tname = $tpl_name;

    $func_name = $this->tpl_template_prefix.$this->_skin
                .$this->tpl_template_sepfix.$tname
								;

		if (!function_exists($func_name)) {

			// 2. получение имён файлов исходника и компилята
			$this->spawnCompiler();
			$source = $this->compiler->_TemplateCompile($tpl_string);
			eval ("function $func_name(&\$params,\$tpl){ ?".">$source<?"."php }");
			$this->register_plugin_action(array($tpl_name, $func_name));
		}

		return $func_name;
	}
  public function parseTemplate ($tpl_name)
  /**
   * парсинг шаблона и возвращение результата.
   *
   * @param string $tpl_name
   * @return string
   */
  {
    // 0. смотрим в кеше
		if (isset($this->ep_actions[$tpl_name]))
			return $this->ep_actions[$tpl_name];

    // 1. split tplname by :
    $a = explode( ':', $tpl_name );
    $name0 = $a[0]; // имя файла
    if (sizeof($a) > 1) $_name = $a[1]; // имя подшаблона
    else                $_name = '';
    // имя до расширения, возможно указывать имя шаблона без расширения
    $_pos = strrpos($tpl_name, ".");
    $name = $_pos ? substr($name0, 0, $_pos) : $name0;
    // имя готовое для кэширования
    $tname = str_replace("/",$this->tpl_template_sepfix, $name);


    $func_name = $this->tpl_template_prefix.$this->_skin
                .$this->tpl_template_sepfix.$tname
                .($_name ? $this->tpl_template_sepfix.$_name : '');

		trace(__METHOD__."($tpl_name) parse for '$func_name'");

		if (!function_exists($func_name)) {

			// 2. получение имён файлов исходника и компилята
			$file_cached = $this->tpl_cache_name. $tname.".php";

			trace(__METHOD__." should be cached as: ".$file_cached);

			// 3. проверка наличия в кэше/необходимости рекомпиляции
			$recompile = $this->tpl_compile != TPL_COMPILE_NEVER || !file_exists( $file_cached );
			if ($recompile) {
				$file_source = $this->findTemplate( $name );
				if ($file_source && ($this->tpl_compile != TPL_COMPILE_ALWAYS)) {
					if (@filemtime($file_cached) >= filemtime($file_source)) {
						$recompile = false;
					}
				}
			}

			// 4. перекомпиляция
			if ($recompile) {
				$this->spawnCompiler();
				trace( '[TemplateEngine] ('.$name.') source:'.$file_source );
				trace( '[TemplateEngine] ('.$name.') cache to:'.$file_cached );
				$this->compiler->TemplateCompile( $this->_skin, $name, $tname, $file_source, $file_cached );
			}
			// 5. парсинг-таки

			include_once $file_cached;

			if (!function_exists($func_name)) {
				error( '[TemplateEngine] ('.$name.') subtemplate "'.$_name.'" is not exists', 1 );
				trace( '[TemplateEngine] ('.$name.') create stub for undefined template function "'.$func_name.'"' );
				eval ("function $func_name(\$tpl){}");
				$this->register_plugin_action(array($tpl_name, $func_name));
			}
		}

		return $func_name;
  }
	public function parseAction ($action_name)
	/**
	 * вызов действия
	 *
	 * @param string $action_name
	 * @return string
	 */
	{
    // 0. смотрим в кеше
		if (isset($this->ep_actions[$action_name]))
			return $this->ep_actions[$action_name];

		// by kuso@npj, 16-09-2004
		$action_name_for_cache = str_replace('/', '__', $action_name);

		# HACK: lucky: 2008-02-25
		# сейчас не осталось ни одного подобного метода, и не понятно, нужно-ли это вообще..
		# посему комменчу до поры..
		#
		#//проверяем - а не системный ли это экшен?
		#$func_name = 'action_'.$action_name_for_cache;
		#
		#if (method_exists($this, $func_name) )
		#  return array($this, &$func_name);

		//генерируем имя функции
		$func_name = $this->tpl_action_prefix.$this->_skin.
								 $this->tpl_template_sepfix.$action_name_for_cache;

		trace(__METHOD__."($action_name) parse for '$func_name'");

		//проверяем экшен на существование
		if(!function_exists($func_name) )
		{
			//получение имён файлов для исходника и компилята
			$file_cached = $this->action_cache_name.$action_name_for_cache.".php";

			//проверка на необходимость компиляции
			$recompile = $this->tpl_compile != TPL_COMPILE_NEVER || !file_exists( $file_cached );
			if ($recompile) {
				$file_source = $this->findAction( $action_name );
				if ($file_source && ($this->tpl_compile != TPL_COMPILE_ALWAYS)) {
					if (@filemtime($file_cached) >= filemtime($file_source)) {
						$recompile = false;
					}
				}
			}

			//откомпилировать функцию
			if ($recompile) {
				$this->spawnCompiler();
				$this->compiler->ActionCompile( $this->_skin, $action_name, $action_name_for_cache, $file_source, $file_cached );
			}

			//подключить функцию
			include_once $file_cached;
		}

		$this->register_plugin_action(array($action_name, $func_name));

		return $func_name;
	}


  // Работа с доменом директорий -------------------------------------------------------------

	public function pushResourceDir ($path)
	{
		$this->tpl_dirs[] = $path;
		$this->_rebuildEnvironment();
	} // end of method pushResourceDir
	public function popResourceDir ()
	{
		$path = array_pop($this->tpl_dirs);
		$this->_rebuildEnvironment();
	} // end of method popResourceDir
	public function findFile ($type, $name, $skin=NULL, $ext=NULL)
	{
		if ($skin) $filename = ($skin ? $skin.'/':'').($type ? $type.'/':'').$name.($ext ? '.'.$ext : '');
		else $filename = ($type ? $type.'/' : '').$name.($ext ? '.'.$ext : '');

		for ($i=count($this->tpl_dirs)-1; $i >= 0; $i--) {
			$path = $this->tpl_dirs[$i].$filename;
				if (@file_exists($path)) return $path;
		}

		if ($skin) return $this->findFile($type, $name, NULL, $ext);
		return false;
	} // end of method findFile
	public function findAction ($action_name)
	{
		return $this->findFile('plugins', 'action_'.$action_name, $this->_skin, 'php');
	} // end of method findAction
  public function findTemplate ($tpl_filename)
  /**
   * возвращает уровень и полный путь к кэш-файлу
   *
   * @param string $tpl_filename
   * @param string $skin
   * @return string
   */
  {
		return $this->findFile($this->tpl_templates_dir, $tpl_filename, $this->_skin, 'html');
  }

  private function spawnCompiler()
  /**
   * порождает компиляторскую часть
   *
   * @return void
   */
  {
    if (!isset($this->compiler)) {
      require_once "TplCompiler.php";
      $this->compiler = new TplCompiler( $this );
			$this->compiler->register_plugins($this->ep_compiler_plugins);
    }
  }

  // Языковые конструкты шаблонного движка (Actions) -----------------------------------------------
  // Системные плугины
  //function action_fo( &$params )
  //{
	//  return 'fo';
  //}
	var $content_type = 'html';
	public $autoescape = true;
	public function convert ($value, $type=NULL)
	{
		if (!$this->autoescape) return $value;
		// если мы знаем тип, и тип совпадает с текущим для $tpl, не эскейпим
		if ($type === $this->content_type) {
			return $value;
		}
		// FIXME : 2008-03-05 : lucky
		// а можно выбирать escape, в зависимости от типа контента (если вдруг знаем)
		$_ = array($value);
		return $this->Action('escape', $_);
	} // end of method convert
	public function getType ($key)
	{
		return isset($this->domain[TPL_DOMAIN_TYPE][$key]) ? $this->domain[TPL_DOMAIN_TYPE][$key] : NULL;
	} // end of method getType

  // Вспомогательные методы шаблонного движка -----------------------------------------------
	public function apm($d, $key, $method)
	{
		if (is_array($d)) { if (isset($d[$key])) return $d[$key]; else return NULL; }
		if ($d instanceof ArrayAccess) { return $d[$key]; }
		if (is_object($d)) {
			if (method_exists($d, $method)) return $d->$method();
			if (isset($d->$key)) return $d->$key; else return NULL;
		}
		return NULL;
	}
	public function am($d, $key, $method)
	{
		if (is_array($d)) { if (isset($d[$key])) return $d[$key]; else return NULL; }
		if ($d instanceof ArrayAccess) { return $d[$key]; }
		if (is_object($d)) {
			if (method_exists($d, $method)) return $d->$method();
		}
		return NULL;
	}
	public function a($d, $key)
	{
		if (is_array($d)) { if (isset($d[$key])) return $d[$key]; else return NULL; }
		if ($d instanceof ArrayAccess) { return $d[$key]; }
		return NULL;
	}

  // Плагины -----------------------------------------------
	public function register_plugins ($config)
	{
		foreach ($config as $v) {
			$plugin_name = $v[0];
			$ps = explode(':', $plugin_name, 2);
			if (sizeof($ps) == 2) {
				include_once $ps[0];
				$plugin_name = $ps[1];
			}
			$m = 'register_plugin_'.$plugin_name;
		  if (is_callable(array($this, $m))) $this->$m($v);
		}
	} // end of function register_plugins

	public $ep_actions = array();
	public function register_plugin_action ($config)
	{
		$name = $config[0];
		$func_name = $config[1];
		$this->ep_actions[$name] = $func_name;
	} // end of method register_plugin_action

	public $ep_compiler_plugins = array();
	public function register_plugin_compiler_plugins ($config)
	{
		$this->ep_compiler_plugins = $config[1];
	} // end of function register_compiler_plugins

	public function _rebuildEnvironment ()
	{
		// сформировать путь из стека директорий
		$path = implode(PATH_SEPARATOR, $this->tpl_dirs);
		$path_hash = md5($path);

		$this->base_cache_name = $this->environment.$this->tpl_template_sepfix
										 .$path_hash.$this->tpl_template_sepfix
										 .$this->_skin;
    // запомнить каталог для кеширования шаблонов
		$this->tpl_cache_name = $this->tpl_cache_dir
			               .$this->base_cache_name
										 .$this->tpl_template_file_prefix;

    // запомнить каталог для кеширования экшенов
		$this->action_cache_name = $this->tpl_cache_dir
			               .$this->base_cache_name
                     .$this->tpl_action_file_prefix;

	} // end of method _rebuildEnvironment


	// HACK : 2008-03-24 : lucky
	// далее хаки для !connect
	public function cacheCss ($files)
	{
		$res = false;

		$file_sources = $source = $css_header = '';
		foreach ($files as $file_source) {
			$s = file_get_contents($file_source);
			$s = $this->parseString($s);
			$file_href = substr($file_source, strlen($this->tpl_base_dir));
			$css_header .= "$file_href\n";
			$source .= "\n\n/* source: {$file_href} */\n\n".$s;
			$file_sources .= $file_sources ? PATH_SEPARATOR . $file_source : $file_source;
		}
		$file_cached = $this->tpl_data_dir.md5($this->base_cache_name . $file_sources).'.css';
		$source = "/* \n{$css_header} \n*/\n$source";
		if ($this->putStaticTextData($file_cached, $source)) {
			$res = array(
				'file'=>$file_cached,
				'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
			);
		}

		return $res;
	} // end of method cacheConcat
	public function isCssCached ($files)
	{
		$res = false;
		do {
			if (!$files) break;

			$file_sources = $source = '';
			foreach ($files as $file_source) {
				$file_sources .= $file_sources ? PATH_SEPARATOR . $file_source : $file_source;
			}
			$file_cached = $this->tpl_data_dir.md5($this->base_cache_name . $file_sources).'.css';
			if (!file_exists($file_cached)) {
				break;
			}
			if ($this->tpl_compile === TPL_COMPILE_NEVER) {
				$res = array(
					'file'=>$file_cached,
					'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
				);
				break;
			}
			foreach ($files as $file_source) {
				if (filemtime($file_cached) < filemtime($file_source)) {
					break 2;
				}
			}
			$file_cached_gz = $file_cached.'.gz';
			$res = array(
				'file'=>$file_cached,
				'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
			);
		} while (false);
		return $res;
	} // end of method isCached
	var $connect_cache_exclude=array(
		'tiny_mce_src.js',
		'tiny_mce_start.js',
		//'jquery.js',
	);
	public function cacheJs ($files)
	{
		$res = array();

		$file_sources = $source = $js_header = '';
		foreach ($files as $file_source) {
			if (!$file_source) continue;
			if ($this->isJsFileExcluded($file_source)) {
				if ($file_sources) {
					$file_cached = $this->tpl_data_dir.md5($this->base_cache_name . $file_sources).'.js';
					$source = "/* \n{$js_header} \n*/\n$source";
					if ($this->putStaticTextData($file_cached, $source)) {
						$res[] = array(
							'file'=>$file_cached,
							'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
						);
					}
					$file_sources = $source = $js_header = '';
				}
				$res[] = array(
					'file'=>$file_source,
					'href'=>$this->tpl_base_path.substr($file_source, strlen($this->tpl_base_dir))
				);
			} else {
				$s = file_get_contents($file_source);
				//$s = $this->parseString($s);
				$file_href = substr($file_source, strlen($this->tpl_base_dir));
				$js_header .= "$file_href\n";
				$source .= "\n\n/* source: {$file_href} */\n\n".$s;
				$file_sources .= $file_sources ? PATH_SEPARATOR . $file_source : $file_source;
			}
		}
		if ($file_sources) {
			$file_cached = $this->tpl_data_dir.md5($this->base_cache_name . $file_sources).'.js';
			$source = "/* \n{$js_header} \n*/\n$source";
			if ($this->putStaticTextData($file_cached, $source)) {
				$res[] = array(
					'file'=>$file_cached,
					'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
				);
			}
		}

		return $res;
	} // end of method cacheConcat
	public function isJsFilesCached ($files)
	{
		$res = array();
		do {
			if (!$files) break;

			$files_ = array();
			foreach ($files as $file_source) {
				if ($this->isJsFileExcluded($file_source)) {
					if ($files_) {
						if ($cached = $this->isJsFilesCached_($files_)) {
							$res[] = $cached;
						} else {
							$res = false; break 2;
						}
					}
					$res[] = array(
						'file'=>$file_source,
						'href'=>$this->tpl_base_path.substr($file_source, strlen($this->tpl_base_dir))
					);
					$files_ = array();
				} else {
					$files_[] = $file_source;
				}
			}
			if ($files_) {
				if ($cached = $this->isJsFilesCached_($files_)) {
					$res[] = $cached;
				} else {
					$res = false;
				}
			}
		} while (false);
		return $res;
	} // end of method isCached
	public function isJsFilesCached_ ($files)
	{
		$res = false;
		do {
			if (!$files) break;

			$file_sources = $source = '';
			foreach ($files as $file_source) {
				$file_sources .= $file_sources ? PATH_SEPARATOR . $file_source : $file_source;
			}
			$file_cached = $this->tpl_data_dir.md5($this->base_cache_name . $file_sources).'.js';
			if (!file_exists($file_cached)) {
				break;
			}
			if ($this->tpl_compile === TPL_COMPILE_NEVER) {
				$res = array(
					'file'=>$file_cached,
					'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
				);
				break;
			}
			foreach ($files as $file_source) {
				if (filemtime($file_cached) < filemtime($file_source)) {
					break 2;
				}
			}
			$file_cached_gz = $file_cached.'.gz';
			$res = array(
				'file'=>$file_cached,
				'href'=>$this->tpl_base_path.substr($file_cached, strlen($this->tpl_base_dir))
			);
		} while (false);
		return $res;
	} // end of method isCached
	public function isJsFileExcluded ($file_source)
	{
		if (!isset($this->_connect_cache_exclude)) {
			$this->_connect_cache_exclude = '';
			$c = '';
			foreach ($this->connect_cache_exclude as $mask) {
				$re = str_replace('\\*', '.*?',preg_quote($mask, '/'));
				$c .= $c ? '|'.$re : $re;
			}
			$this->_connect_cache_exclude = '/(?:'.$c.')$/';
		}
		return preg_match($this->_connect_cache_exclude, $file_source);
	} // end of method isJsFileExcluded


	public function putStaticTextData ($path, $text)
	{
		$status = self::filePutContents($path, $text, $this->static_file_mode);
		if (!$this->disable_gz) {
			$status &= self::filePutContents("$path.gz", gzencode($text, 9), $this->static_file_mode);
		}
		return $status;
	} // end of method filePutCss

	public function putScript ($path, $source)
	{
		$status = self::filePutContents($path, $source, $this->script_file_mode);
	} // end of method putScript

	/**
	 * filePutContents file_put_contents && chmod.
	 *
	 * @param mixed $path
	 * @param mixed $data
	 * @param mixed $mode
	 * @access public
	 * @return void
	 */
	static function filePutContents($path, $data, $mode=NULL)
	{
		$status = file_put_contents($path, $data);
		if ($status && $mode !== NULL) {
			chmod ($path, $mode);
		}
		return $status;
	}
}

